/*
 * SPDX-License-Identifier: GPL-2.0-or-later
 * SPDX-FileName: StringUtils.h
 * SPDX-FileContributor: Dmitry Vedenko
 */

#pragma once

#include <algorithm>
#include <cstring>
#include <cwchar>
#include <string>
#include <string_view>
#include <type_traits>
#include <numeric>

#include <wx/string.h>

inline std::size_t StringLength(const char str)
{
    return 1;
}

inline std::size_t StringLength(const wchar_t str)
{
    return 1;
}

inline std::size_t StringLength(const char* str)
{
    return std::strlen(str);
}

inline std::size_t StringLength(const wchar_t* str)
{
    return std::wcslen(str);
}

template<typename CharType, size_t N>
std::size_t StringLength(const CharType (&)[N])
{
    return N - 1;
}

template<typename StringType>
std::size_t StringLength(const StringType& str)
{
    return str.length();
}

template<template < typename, typename ...> typename ContainerType, typename ResultType, typename SeparatorType, typename ... Rest >
         ResultType Join(
             const ContainerType<ResultType, Rest...>& container,
                                 const SeparatorType& separator)
         {
             if (container.empty()) {
                 return ResultType {};
             }

             const auto sepratorLength = StringLength(separator);
             const auto totalSeparatorLength = sepratorLength * (container.size() - 1);

             ResultType result;

             const auto size = std::accumulate(
                 container.begin(), container.end(), totalSeparatorLength,
                 [](size_t size, const ResultType& item)
    { return size + StringLength(item); });

             result.reserve(size);

             bool first = true;
             for (const auto& item : container) {
                 if (!first) {
                     result += separator;
                 }
                 result += item;
                 first = false;
             }

             return result;
         }

         STRING_UTILS_API std::string  ToLower(const std::string& str);
         STRING_UTILS_API std::string  ToLower(const std::string_view& str);
         STRING_UTILS_API std::string  ToLower(const char* str);
         STRING_UTILS_API std::wstring ToLower(const std::wstring& str);
         STRING_UTILS_API std::wstring ToLower(const std::wstring_view& str);
         STRING_UTILS_API std::wstring ToLower(const wchar_t* str);
         STRING_UTILS_API wxString     ToLower(const wxString& str);

         STRING_UTILS_API std::string  ToUpper(const std::string& str);
         STRING_UTILS_API std::string  ToUpper(const std::string_view& str);
         STRING_UTILS_API std::string  ToUpper(const char* str);
         STRING_UTILS_API std::wstring ToUpper(const std::wstring& str);
         STRING_UTILS_API std::wstring ToUpper(const std::wstring_view& str);
         STRING_UTILS_API std::wstring ToUpper(const wchar_t* str);
         STRING_UTILS_API wxString     ToUpper(const wxString& str);

         namespace details {
             inline const char* begin(const char* str) noexcept
             {
                 return str;
             }

             inline const char* end(const char* str) noexcept
             {
                 if (str == nullptr) {
                     return nullptr;
                 }

                 return str + StringLength(str);
             }

             inline const wchar_t* begin(const wchar_t* str) noexcept
             {
                 return str;
             }

             inline const wchar_t* end(const wchar_t* str) noexcept
             {
                 if (str == nullptr) {
                     return nullptr;
                 }

                 return str + StringLength(str);
             }
         } // namespace details

         template<typename HayType, typename PrefixType>
bool IsPrefixed(
    const HayType& hay, const PrefixType& prefix)
{
    if (StringLength(hay) < StringLength(prefix)) {
        return false;
    }

    using namespace std;
    using namespace details;

    const auto prefixBegin = begin(prefix);
    const auto prefixEnd = end(prefix);
    const auto hayBegin = begin(hay);

    return std::mismatch(prefixBegin, prefixEnd, hayBegin).first == prefixEnd;
}

template<typename HayType, typename PrefixType>
bool IsPrefixedInsensitive(const HayType& hay, const PrefixType& prefix)
{
    return IsPrefixed(ToLower(hay), ToLower(prefix));
}
